#include "common/pch.h"
#include "metadata/metamgr.h"

#include <format>

#include <cftc/reflect/class.h>
#include <cftc/reflect/field.h>
#include <cftc/reflect/annotation.h>
#include <cftc/util/strutils.h>
#include <cftc/util/sysutils.h>
#include <cftf/loger/syslog.h>
#include <cftf/mysql/dbpool.h>
#include <cftf/meta/loader.h>

#include "metadata/item.h"
#include "metadata/creaturedata.h"
#include "metadata/instance.h"
#include "metadata/creatureinfo.h"
#include "metadata/equipment_info.h"
#include "metadata/equipment_info_raw.h"
#include "metadata/gameobjectdata.h"
#include "metadata/pool_creature.h"
#include "metadata/pool_creature_template.h"
#include "metadata/pool_gameobject.h"
#include "metadata/pool_gameobject_template.h"
#include "metadata/pool_template.h"
#include "metadata/pool_pool.h"
#include "metadata/player_create_info.h"
#include "metadata/player_create_info_action.h"
#include "metadata/player_create_info_item.h"
#include "metadata/player_create_info_spell.h"
#include "metadata/player_levelstats.h"
#include "metadata/areatrigger_teleport.h"
#include "metadata/creature_model_info.h"
#include "metadata/player_xp_for_level.h"

namespace mt
{
   
    static bool UnMarshalField(int depth, cftf::mysql::DataSet* ds, cftc::reflect::Object* obj, const std::shared_ptr<cftc::reflect::Field> field, int idx)
    {
        if (depth > 1) {
            abort();
        }
        auto wowdb_anno = field->GetAnnotation("wow_meta");
        if (!wowdb_anno) {
            abort();
        }
        if (wowdb_anno->HasOption("element_size")) {
            return true;
        }
        std::string field_name;
        if (field->GetType()->GetKind() != A8ReflectTypeKind_e::kObject) {
            field_name = wowdb_anno->GetOption("field_name")->Get<std::string>();
            if (idx >= 0) {
                field_name = std::format("{}_{}", field_name, idx + 1);
            }
        }
        auto type = field->GetType();
        void* data = (unsigned char*)obj + field->GetOffset();
        auto in = ds->GetRawByFieldName(field_name);
#pragma warning(push)
#pragma warning(error: 4062)
        if (type->IsPointer()) {
            if (!in) {
                return true;
            }
            type->TryInPlaceNew(data);
        }
        void* rawdata_ptr = type->GetRawDataPtr(data);
        switch (type->GetKind())
        {
        case A8ReflectTypeKind_e::kNone:
        {
            abort();
            break;
        }
        case A8ReflectTypeKind_e::kInt8:
        {
            *(char*)rawdata_ptr = in->Get<char>();
            break;
        }
        case A8ReflectTypeKind_e::kUInt8:
        {
            *(unsigned char*)rawdata_ptr = in->Get<unsigned char>();
            break;
        }
        case A8ReflectTypeKind_e::kInt16:
        {
            *(short*)rawdata_ptr = in->Get<short>();
            break;
        }
        case A8ReflectTypeKind_e::kUInt16:
        {
            *(unsigned short*)rawdata_ptr = in->Get<unsigned short>();
            break;
        }
        case A8ReflectTypeKind_e::kInt32:
        {
            *(int*)rawdata_ptr = in->Get<int>();
            break;
        }
        case A8ReflectTypeKind_e::kUInt32:
        {
            *(unsigned int*)rawdata_ptr = in->Get<unsigned int>();
            break;
        }
        case A8ReflectTypeKind_e::kInt64:
        {
            *(long long*)rawdata_ptr = in->Get<long long>();
            break;
        }
        case A8ReflectTypeKind_e::kUInt64:
        {
            *(unsigned long long*)rawdata_ptr = in->Get<unsigned long long>();
            break;
        }
        case A8ReflectTypeKind_e::kFloat:
        {
            *(float*)rawdata_ptr = in->Get<float>();
            break;
        }
        case A8ReflectTypeKind_e::kDouble:
        {
            *(double*)rawdata_ptr = in->Get<double>();
            break;
        }
        case A8ReflectTypeKind_e::kString:
        {
            *(std::string*)rawdata_ptr = in->Get<std::string>();
            break;
        }
        case A8ReflectTypeKind_e::kObject:
        {
            abort();
            //JsonToObject(in, (cftc::reflect::Object*)rawdata_ptr);
            break;
        }
        case A8ReflectTypeKind_e::kXObject:
        {
            break;
        }
        case A8ReflectTypeKind_e::kVector:
        case A8ReflectTypeKind_e::kList:
        {
            auto element_size_constant = cftc::reflect::Type::GetConstant(wowdb_anno->GetOption("element_size")->Get<std::string>());
            std::size_t element_size = wowdb_anno->GetOption("element_size")->Get<std::size_t>();
            if (element_size_constant) {
                element_size = element_size_constant->Get<std::size_t>();
            }
            if (element_size <= 0) {
                abort();
            }
            if (field->GetType()->GetElementType()->GetKind() != A8ReflectTypeKind_e::kObject) {
                abort();
            }
            for (std::size_t i = 0; i < element_size; ++i) {
                cftc::reflect::Object* ele = (cftc::reflect::Object*)type->NewElement(data, &i);   
                ele->GetClass()->TraverseFields(
                    [ele, ds, depth, i](const std::shared_ptr<cftc::reflect::Field> field) -> bool
                    {
                        UnMarshalField(depth + 1, ds, ele, field, (int)i);
                        return true;
                    }
                );
            }
            break;
        }
        case A8ReflectTypeKind_e::kMap:
        {
            abort();
            break;
        }
        SWITCH_ENUM_CHECK()
        }
#pragma warning(pop)
        return true;
    }

    static void UnMarshal(cftc::reflect::Object* obj, cftf::mysql::DataSet* ds)
    {
        obj->GetClass()->TraverseFields(
            [obj, ds](const std::shared_ptr<cftc::reflect::Field> field) -> bool
            {
                UnMarshalField(0, ds, obj, field, -1); 
                return true;
            }
        );
    }

    MetaMgr::MetaMgr()
    {
        loader_ = std::make_shared<cftf::meta::Loader>();
        conf_ = cftc::ducktype::XObject::ReadFromIniFile(R"(D:\opensource\MangoszeroServer_x64_release\server_releasex64\mangosd.conf)");
    }

    void MetaMgr::Init()
    {
        cftf::loger::SysLog::Instance().Info("MetaMgr.Init start");
        RegisterDataSource();
        {
            loader_->RegMetaTable<Item>("item_template", "entry");
            loader_->RegMetaTable<CreatureData>("creature", "guid");
            loader_->RegMetaTable<CreatureInfo>("creature_template", "Entry");
            loader_->RegMetaTable<EquipmentInfo>("creature_equip_template", "entry");
            loader_->RegMetaTable<EquipmentInfoRaw>("creature_equip_template_raw", "entry");
            loader_->RegMetaTable<GameObjectData>("gameobject", "guid");
            loader_->RegMetaTable<PoolCreature>("pool_creature", "guid");
            loader_->RegMetaTable<PoolCreatureTemplate>("pool_creature_template", "guid");
            loader_->RegMetaTable<PoolGameObject>("pool_gameobject", "guid");
            loader_->RegMetaTable<PoolGameObjectTemplate>("pool_gameobject_template", "guid");
            loader_->RegMetaTable<PoolPool>("pool_pool", "pool_id");
            loader_->RegMetaTable<PoolTemplate>("pool_template", "entry");
            loader_->RegMetaTable<PlayerCreateInfo>("playercreateinfo", "");
            loader_->RegMetaTable<PlayerCreateInfoAction>("playercreateinfo_action", "");
            loader_->RegMetaTable<PlayerCreateInfoItem>("playercreateinfo_item", "");
            loader_->RegMetaTable<PlayerCreateInfoSpell>("playercreateinfo_spell", "");
            loader_->RegMetaTable<PlayerLevelStats>("player_levelstats", "");
            loader_->RegMetaTable<AreaTrigger>("areatrigger_teleport", "id");
            loader_->RegMetaTable<CreatureModelInfo>("creature_model_info", "modelid");
            loader_->RegMetaTable<PlayerXpForLevel>("player_xp_for_level", "lvl");
        }
        Load();        
        cftf::loger::SysLog::Instance().Info("MetaMgr.Init end");
    }

    void MetaMgr::UnInit()
    {       
    }

    std::string MetaMgr::GetDataDir()
    {
        return R"(D:\BaiduNetdiskDownload\wow_cn_1.12.3)";
    }

    void MetaMgr::Load()
    {
        loader_->Load(
            [](const std::shared_ptr<cftf::meta::TableConf> conf)
            {
                cftf::loger::SysLog::Instance().Info(std::format("metamgr.load {} begin", conf->GetTableName()));
                std::string sql = std::format("select * from `{}`;", conf->GetTableName());
                auto wow_meta_anno = conf->GetTableMetaClass()->GetAnnotation("wow_meta");
                if (wow_meta_anno && wow_meta_anno->HasOption("custom_loader_sql")) {
                    sql = cftc::reflect::Type::GetConstant(wow_meta_anno->GetOption("custom_loader_sql")->Get<std::string>())->Get<std::string>();                    
                }
                cftf::mysql::DBPool::Instance().RawQuery(
                    (int)DataSource_e::World,
                    sql,
                    {},
                    [conf](const std::shared_ptr<cftf::mysql::QueryRsp> rsp)
                    {
                        if (!rsp->IsOk()) {
                            abort();
                        }
                        std::size_t readed_num = 0;
                        long long tick = cftc::util::XGetTickCount();
                        cftf::loger::SysLog::Instance().Info(std::format("metamgr.load {}/{}", readed_num, rsp->GetDataSet()->GetNumRows()));
                        while (!rsp->GetDataSet()->IsEof()) {
                            ++readed_num;
                            conf->UnMarshalAndAdd(
                                [rsp](cftc::reflect::Object* obj) -> bool
                                {
                                    UnMarshal(obj, rsp->GetDataSet().get());
                                    return true;
                                });
                            if (cftc::util::XGetTickCount() - tick > 1000 * 3) {
                                tick = cftc::util::XGetTickCount();
                                cftf::loger::SysLog::Instance().Info(std::format("metamgr.load {}/{}", readed_num, rsp->GetDataSet()->GetNumRows()));
                            }
                            rsp->GetDataSet()->Next();
                        }
                        cftf::loger::SysLog::Instance().Info(std::format("metamgr.load {}/{}", readed_num, rsp->GetDataSet()->GetNumRows()));
                    })->Await();
                cftf::loger::SysLog::Instance().Info(std::format("metamgr.load {} end", conf->GetTableName()));
            }); 
    }

    void MetaMgr::RegisterDataSource()
    {
        {
            std::string database_info = mt::MetaMgr::Instance().GetConf()->At("MangosdConf.LoginDatabaseInfo")->AsXValue().Get<std::string>();
            if (!database_info.empty() && database_info.at(0) == '"') {
                database_info.erase(database_info.begin());
            }
            if (!database_info.empty() && database_info.at(database_info.size() - 1) == '"') {
                database_info.erase(database_info.size() - 1);
            }
            std::vector<std::string> strs;
            cftc::util::Split(database_info, strs, ';');
            cftf::mysql::DBPool::Instance().RegisterDataSource(
                (int)DataSource_e::Login,
                strs.at(0),
                cftc::ducktype::XValue(strs.at(1)).Get<int>(),
                strs.at(2),
                strs.at(3),
                strs.at(4)
            );
        }
        {
            std::string database_info = mt::MetaMgr::Instance().GetConf()->At("MangosdConf.WorldDatabaseInfo")->AsXValue().Get<std::string>();
            if (!database_info.empty() && database_info.at(0) == '"') {
                database_info.erase(database_info.begin());
            }
            if (!database_info.empty() && database_info.at(database_info.size() - 1) == '"') {
                database_info.erase(database_info.size() - 1);
            }
            std::vector<std::string> strs;
            cftc::util::Split(database_info, strs, ';');
            cftf::mysql::DBPool::Instance().RegisterDataSource(
                (int)DataSource_e::World,
                strs.at(0),
                cftc::ducktype::XValue(strs.at(1)).Get<int>(),
                strs.at(2),
                strs.at(3),
                strs.at(4)
            );
        }
        {
            std::string database_info = mt::MetaMgr::Instance().GetConf()->At("MangosdConf.CharacterDatabaseInfo")->AsXValue().Get<std::string>();
            if (!database_info.empty() && database_info.at(0) == '"') {
                database_info.erase(database_info.begin());
            }
            if (!database_info.empty() && database_info.at(database_info.size() - 1) == '"') {
                database_info.erase(database_info.size() - 1);
            }
            std::vector<std::string> strs;
            cftc::util::Split(database_info, strs, ';');
            cftf::mysql::DBPool::Instance().RegisterDataSource(
                (int)DataSource_e::Character,
                strs.at(0),
                cftc::ducktype::XValue(strs.at(1)).Get<int>(),
                strs.at(2),
                strs.at(3),
                strs.at(4)
            );
        }
    }

}